/*********************************************************************************************************
 *
 *
 *                      这个demo用于实现预处理模块，
 *                              1.读取并分析boost文档的html内容，
 *                              2.解析出每个文档的标题、url、正文（去除html标签）
 *                              3.最终将结果输出成一个行文本文件
 *
 *
 *********************************************************************************************************/


#include<iostream>
#include<string>
#include<vector>
#include<unordered_map>

//boost中包含大量hpp头文件（声明定义放在一起，直接包含就能用）（header only 风格）
#include<boost/filesystem/path.hpp>
#include<boost/filesystem/operations.hpp>
#include"../common/util.hpp"

using namespace std;

//定义一些相关的变量和结构体
string g_input_path = "../data/input";          //boost的html文件的读取位置
string g_output_path = "../data/tmp/row_input";           //预处理模块的输出结果对应位置

//应该解析到的html信息
struct DocInfo
{
  //标题
  string title;
  //url
  string url;
  //正文
  string content;
};


//枚举API
bool EnumFile(const string& input_path, vector<string>* file_list)
{
  //枚举使用boost完成，先安装boost  yum install boost-devel (1.53)
  namespace fs = boost::filesystem;
 
  //设置起始目录，进行错误预判
  fs::path root_path(input_path);
  if(!fs::exists(root_path))
  {
    cout<<"当前目录不存在"<<endl;
    return false;
  }
  
  //进行递归枚举时使用到一个核心的   迭代器类  recursive_directory_iterator
  //无参构造时，迭代器就指向最后一个文件（哨兵）
  //迭代器循环实现时，就可以完成递归
  fs::recursive_directory_iterator end_iter;
  for(fs::recursive_directory_iterator iter(root_path);iter!= end_iter;++iter)
  {
    //如果当前目录不是普通html文件，如果是目录，就跳，继续遍历枚举
    if(!fs::is_regular(*iter))
    {
        continue;
    }
    //如果当前路径是其他文件，不是html文件，就跳过，继续
    if(iter->path().extension() != ".html")
    {
      continue;
    }
    //将当前结果保存到输出结果file_list中     file_list是vector<string>所以iter的目录也应该转为string
    file_list->push_back(iter->path().string());
  }

  return true;
}


//解析API
bool ParseTitle(const string& html,string* title);
bool ParseUrl(const string& file_path,string* url);
bool ParseContent(const string& html,string* content);
bool ParserFile(const string& file_path,DocInfo* doc_info)
{
  //1.先读取文件内容，将文件内容追加到一个html文件中
  //       这个功能通过一个Read函数完成，read是最基本的函数，所以放在common下的util中完成
  string html;
  bool ret =common::util::Read(file_path,&html);
  if(ret == false)
  {
    cout<<"文件读取失败"<<endl;
    return false;
  }
  //2.根据文件中的内容解析出标题填充doc_info.title 
  ret = ParseTitle(html,&doc_info->title);
  if(ret == false)
  {
    cout<<"解析标题失败"<<file_path<<endl;
    return false;
  }
  //3.根据文件路径，构造出对应在线文档的url
  ret = ParseUrl(file_path,&doc_info->url);
  if(ret == false)
  {
    cout<<"解析URL失败"<<file_path<<endl;
    return false;
  }
  //4.根据文件内容，去除标签作为doc_info的正文
  ret = ParseContent(html,&doc_info->content);
  if(ret == false)
  {
    cout<<"解析正文失败！"<<file_path<<endl;
    return false;
  }
  return true;
}


//解析html标题标题API
bool ParseTitle(const string& html,string* title)
{
  size_t beg = html.find("<title>");
  if(beg == string::npos)
  {
    cout<<"没有找到<title>标签"<<endl;
    return false;
  }
  size_t end = html.find("</title>");
  if(end == string::npos)
  {
    cout<<"没有找到</title>标签"<<endl;
    return false;
  }
  beg+= string("<title>").size();      //<title> TITLE </title>; 
  if(beg >= end)
  {
    cout<<"标签位置不合法"<<endl;
    return false;
  }
  //basic_string::substr(size_type pos=0,size_type n =npos)const;  返回一个从0~npos的字符串（缺省）
  *title = html.substr(beg,end - beg);
  return true;
}

//解析URL的API
//根据本地的路径获取在线路径
bool ParseUrl(const string& file_path,string* url)
{
  //本地路径形如：../data/input/html/thread.html 
  //在线路径形如：https://www.boost.org/doc/libs/1_53_0/doc/html/thread.html 
  //可以发现，后半部分是一样的，只需要  摘取在线url的头和当前路径的尾进行拼接即可得到当前文件的在线url
  string url_head = "https://www.boost.org/doc/libs/1_53_0/doc/";
  string url_tail = file_path.substr(g_input_path.size());   //g_input_path = "../data/input";
  *url = url_head + url_tail;
  return true;
}

//解析正文的API
//去除html文件中的标签
//html的格式是： <标签>正文<标签>  
//   可以引入一bool值，表示当前是否为正文，遇到<说明是标签，遇到>标签结束，后面是正文
bool ParseContent(const string& html,string* content)
{
   bool is_content = true;
   for(auto c : html)
   {
     if(is_content)
     {
       //当前是正文
       if(c == '<')
       {
         //当前遇到标签了
         is_content = false;
       }
       else 
       {
         //对\n进行特殊处理,预期输出的结果是一个行文本，即每个文件对应一行，所以html中的换行都应该替换为其他字符
         //  这里使用
         if(c == '\n')
         {
           c=' ';
         }
         content->push_back(c);
       }
     }
     else 
     {
       //当前是标签
       if(c == '>')
       {
         //标签结束了
         is_content = true;
       }
       else 
       {
         //标签内容都忽略掉
        continue;
       }
     }
   }

   return true;
}


//output写入API
//ofstream类 是没有拷贝构造的，传参数时，只能传指针或引用
//这里要对ofstream进行写操作，则不能加const
void WriteOutput(std::ofstream& ofstream,const DocInfo& doc_info)
{
  //每个doc_info就是一行
  //如果直接ofstream<<doc_info.title<<doc_info.url<<doc_info.content<<endl;就会产生粘包问题
  //      粘包问题是对于字节流来说的，文件、管道、TCP都是面向字节流的，读文件的时候就分不清楚 每个部分
  //            解决方法：1.约定长度  2.使用分隔符（但间隔符必须是正文中一定不能出现的，否则就会二义，或者就需要使用编码）
  //这里使用不可见分隔符，一般不会在文本中出现，ASCII前面几个都是
  ofstream<<doc_info.title<<'\3'<<doc_info.url<<'\3'<<doc_info.content<<endl;
}


//预处理的核心流程：
//1.把input中的所有html文件枚举出来
//        C++标准库中没有枚举的API，boost中有枚举目录的API可以直接使用（boost中直接实现递归枚举）
//        定义一个EnumFile(input_path,vector<string>);将输入路径中的文件路径写入到vector
//2.根据枚举出来的结果依次读取每个文件的内容，并进行解析
//3.把解析结果写入最终的文件中

int main()
{
  //1.进行枚举路径
  vector<string> file_list;
  bool ret = EnumFile(g_input_path,&file_list);
  if(ret == false)
  {
    cout<< "枚举路径失败" << endl;
    return 1;
  }


  //2.遍历枚举出来的路径，对每个文件，单独进行处理
  DocInfo doc_info;
  std::ofstream output_file(g_output_path.c_str());
  //output_file.open("../data/tmp/test.c",ios::out);
  if(!output_file.is_open())
  {        
    cout<<"output文件打开失败"<<endl;
    return 1;
  }
  for(const auto& file_path: file_list)
  {
    cout<<file_path<<endl;
    //读取每个文件的内容，将文件内容输出为标题、url、正文的形式(解析)
    //通过一个函数负责解析工作
    ret = ParserFile(file_path,&doc_info);
    if(!ret)
    {
      cout<<"解析文件失败"<<file_path<<endl;
      continue;
    }


    //3.将解析到的结果放入指定的行文本文件
    //定义文件流，注意卸载循环外（上面）
    //将解析到的内容及doc_info 放入ofstream
    WriteOutput(output_file,doc_info);
  }


  output_file.close();
  return 0;
}






